home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995…tember: Reference Library / Dev.CD Sep 95 RL / Dev.CD Sep 95 RL.toast / mac / Technical Documentation / develop / develop Issue 6 code / TCP / NewsWatcher / NW Source / Shared Code / Reusable Source / nntp.c < prev    next >
Encoding:
Text File  |  1995-05-01  |  38.9 KB  |  1,463 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     nntp.c
  4.  
  5.     This reentrant and reusable module implements an interface to NNTP 
  6.     (USENET news) servers.
  7.     
  8.     The following mandatory functions handle initialization and idle time
  9.     tasks:
  10.     
  11.         NntpInit - Initialize the module.
  12.         NntpIdle - Perform idle time tasks.
  13.     
  14.     The following functions work with NNTP streams:
  15.     
  16.         NntpOpen - Open an NNTP stream.
  17.         NntpClose - Close an NNTP stream.
  18.         NntpAbort - Abort an NNTP stream.
  19.         NntpSetStreamOptions - Set stream options.
  20.         NntpGetGroupNames - Get group names.
  21.         NntpGetGroupInfo - Get info for a single group.
  22.         NntpGetMultipleGroupInfo - Get info for multiple groups.
  23.         NntpGetHeaders - Get selected article headers.
  24.         NntpGetArticle - Get an article.
  25.         NntpPostArticle - Post an article.
  26.         NntpAuthorize - Authorize user.
  27.         NntpGetHello - Get server hello message.
  28.         NntpGetHelp - Get server help text.
  29.         NntpGetIPAddr - Get server IP address.
  30.         NntpGetServerErrInfo - Get server error information.
  31.     
  32.     You must call memutil.c/InitMemUtil, net.c/NetInit, and nntp.c/NntpInit
  33.     before calling any of the other functions in this module. You also must call 
  34.     both the NntpIdle and the NetIdle function in your idle loop, and the NetTerm 
  35.     function at program termination.
  36.         
  37.     A "stream" is an abstraction representing a bidirectional network connection
  38.     to an NNTP server. A stream is represented as a variable of type "NntpStreamRef". 
  39.     These stream references are opaque. You may copy them and pass them as parameters 
  40.     to functions in nntp.c, but you are prohibited from accessing the contents of
  41.     the memory blocks pointed to by the references. Only the functions in 
  42.     nntp.c are permitted to manipulate the contents of these blocks.
  43.         
  44.     The functions return a value of type OSErr as the function result:
  45.     
  46.         noErr                    no error occurred
  47.         nntpServerErr            server error
  48.         nntpNoSuchGroupErr        no such group or group access restriction
  49.         nntpNoSuchArticleErr    no such article
  50.         nntpAuthFailedErr        authorization failed
  51.         other                    any other OS or Toolbox error code
  52.         
  53.     If the function result is nntpServerErr, the NntpGetServerErrInfo function can
  54.     be called to get information about the server error. 
  55.     
  56.     On server errors, no such group errors, and no such article errors, the stream 
  57.     is still open and allocated on return to the caller and may be reused.
  58.     
  59.     If an OS or Toolbox error occurs, or if authorization fails, the connection to 
  60.     the news server is aborted, but the NNTP stream is left allocated. The module 
  61.     will automatically attempt to reestablish a new connection the next time the 
  62.     stream is used. Note that this is a major difference between nntp.c and the ftp.c 
  63.     and smtp.c modules.
  64.     
  65.     NntpOpen is an exception to these rules. For this function, if the return value is
  66.     noErr, nntpServerErr, or nntpAuthFailedErr, then the stream is allocated and can
  67.     be reused. If the return value is an OS or Toolbox error code, however, then the
  68.     stream is not allocated, and cannot be reused.
  69.     
  70.     All group names, header names, message id, and pattern strings passed as parameters
  71.     to functions in this module are restricted to a maximum of 127 characters. If they 
  72.     exceed this maximum, they are truncated.
  73.     
  74.     The following options are associated with each stream:
  75.     
  76.         idleTime        stream is automatically closed if idle for this 
  77.                         many minutes, or 0 for no automatic close
  78.         useXPAT            true to use XPAT command for searches
  79.         sendModeReader    true to send MODE READER command after connect
  80.         batchedCmds        true to use batched GROUP commands
  81.         newConnection    true to establish new connection before getting
  82.                         group info
  83.         authOnConnect    true to authorize on each connection
  84.         username        authorization username
  85.         password        authorization password
  86.         
  87.     The default values are idleTime=0, useXPAT=false, sendModeReader=true, 
  88.     batchedCmds=false, newConnection=true, and authOnConnect=false.  
  89.     See the individual function descriptions more for more details.
  90.     
  91.     Copyright © 1994-1995, Northwestern University.
  92.  
  93. ----------------------------------------------------------------------------*/
  94.  
  95. #include <stdlib.h>
  96. #include <string.h>
  97. #include <stdio.h>
  98. #include <ctype.h>
  99.  
  100. #include "def.h"
  101. #include "nntp.h"
  102. #include "net.h"
  103. #include "strutil.h"
  104. #include "qsort.h"
  105. #include "memutil.h"
  106.  
  107.  
  108.  
  109. /* Types. */
  110.  
  111. typedef struct TStream {
  112.     NetStreamRef netStream;                /* net stream reference, or nil if closed */
  113.     NntpStreamOptions options;            /* stream options */
  114.     Boolean serverHasXPAT;                /* true if server supports XPAT command */
  115.     unsigned long addr;                    /* server IP address */
  116.     unsigned short port;                /* server port number */
  117.     CStr255 helloMsg;                    /* hello message */
  118.     char curGroup[128];                    /* current group on server */
  119.     unsigned long lastTransactionTime;    /* time of last transaction on stream */
  120.     struct TStream **next;                /* handle to next open NNTP stream */
  121. } TStream, *TStreamPtr, **TStreamHandle;
  122.  
  123. typedef struct TGetMultipleGroupInfo {    
  124.     NntpGroupInfoHandle groupInfo;        /* handle to group info array */
  125.     long index;                            /* current index in group info array */
  126.     Boolean serverError;                /* true if server error encountered */
  127. } TGetMultipleGroupInfo;
  128.  
  129.  
  130.  
  131. /*    Global variables. */
  132.  
  133. static TStreamHandle gStreamList = nil;        /* handle to list of open NNTP streams */
  134. static NntpGiveTimeFunction gGiveTime;        /* GiveTime function */
  135.  
  136.  
  137.  
  138. /*----------------------------------------------------------------------------
  139.     SetLastTransactionTime 
  140.     
  141.     Record the transaction time on an NNTP stream.
  142.     
  143.     Entry:    s = stream reference.
  144. ----------------------------------------------------------------------------*/
  145.  
  146. static void SetLastTransactionTime (TStreamHandle s)
  147. {
  148.     unsigned long lastTransactionTime;
  149.     
  150.     GetDateTime(&lastTransactionTime);
  151.     (**s).lastTransactionTime = lastTransactionTime;
  152. }
  153.  
  154.  
  155.  
  156. /*----------------------------------------------------------------------------
  157.     SetCurrentGroup 
  158.     
  159.     Record the current group on an NNTP stream.
  160.     
  161.     Entry:    s = stream reference.
  162.             group = group name.
  163. ----------------------------------------------------------------------------*/
  164.  
  165. static void SetCurrentGroup (TStreamHandle s, char *group)
  166. {
  167.     short len;
  168.     
  169.     len = strlen(group);
  170.     if (len > 127) len = 127;
  171.     BlockMoveData(group, (**s).curGroup, len);
  172.     *((**s).curGroup + len) = 0;
  173. }
  174.  
  175.  
  176.  
  177. /*----------------------------------------------------------------------------
  178.     Authorize 
  179.     
  180.     Authorize user.
  181.     
  182.     Entry:    s = stream reference.
  183.             
  184.     Exit:    function result = result code.
  185.     
  186.     If authorization fails the function returns nntpAuthFailedErr.
  187.     In this case, the net stream is closed.
  188. ----------------------------------------------------------------------------*/
  189.  
  190. static OSErr Authorize (TStreamHandle s)
  191. {
  192.     NetStreamRef netStream;
  193.     char username[32], password[32];
  194.     CStr255 command, response;
  195.     long responseCode;
  196.     OSErr err = noErr;
  197.  
  198.     strcpy(username, (**s).options.username);
  199.     strcpy(password, (**s).options.password);
  200.  
  201.     if (*username == 0 || *password == 0) return noErr;
  202.     
  203.     netStream = (**s).netStream;
  204.  
  205.     sprintf(command, "AUTHINFO USER %s", username);
  206.     err = NetCommand(netStream, command, &responseCode, response);
  207.     if (err != noErr) goto exit1;
  208.     if (responseCode < 300 || responseCode > 399) goto exit2;
  209.     
  210.     sprintf(command, "AUTHINFO PASS %s", password);
  211.     err = NetCommand(netStream, command, &responseCode, response);
  212.     if (err != noErr) goto exit1;
  213.     if (responseCode < 200 || responseCode > 299) goto exit2;
  214.     
  215.     return noErr;
  216.     
  217. exit1:
  218.  
  219.     (**s).netStream = nil;
  220.     return err;
  221.     
  222. exit2:
  223.  
  224.     NetClose(netStream);
  225.     (**s).netStream = nil;
  226.     return nntpAuthFailedErr;
  227. }
  228.  
  229.  
  230.  
  231. /*----------------------------------------------------------------------------
  232.     ReOpenConnection 
  233.     
  234.     Reopen a connection to an NNTP server if necessary.
  235.     
  236.     Entry:    s = stream reference.
  237.     
  238.     Exit:    function result = result code.
  239. ----------------------------------------------------------------------------*/
  240.  
  241. static OSErr ReOpenConnection (TStreamHandle s)
  242. {
  243.     NetStreamRef netStream;
  244.     CStr255 command, response;
  245.     long responseCode;
  246.     OSErr err = noErr;
  247.     
  248.     if ((**s).netStream != nil) return noErr;
  249.     
  250.     NetIdle();
  251.     
  252.     err = NetOpen((**s).addr, (**s).port, true, &netStream, &responseCode, response);
  253.     if (err != noErr) return err;
  254.     
  255.     (**s).netStream = netStream;
  256.     *(**s).curGroup = 0;
  257.     strcpy((**s).helloMsg, response);    
  258.     
  259.     if (responseCode != 200 && responseCode != 201) return nntpServerErr;
  260.  
  261.     if ((**s).options.sendModeReader) {
  262.         strcpy(command, "MODE READER");
  263.         err = NetCommand(netStream, command, &responseCode, response);
  264.         if (err != noErr) goto exit;
  265.     }
  266.     
  267.     if ((**s).options.authOnConnect) {
  268.         err = Authorize(s);
  269.         if (err != noErr) return err;
  270.     }
  271.  
  272.     SetLastTransactionTime(s);
  273.  
  274.     return noErr;
  275.     
  276. exit:
  277.  
  278.     (**s).netStream = nil;
  279.     return err;
  280. }
  281.  
  282.  
  283. /*----------------------------------------------------------------------------
  284.     DoOneGroupCmdResponse 
  285.     
  286.     Process one GROUP command response for batched GROUP commands.
  287.     
  288.     Entry:    responseCode = resonse code.
  289.             response = response string.
  290.             userDataPtr = pointer to TGetMultipleGroupInfo struct.
  291.             
  292.     This function is used by NntpGetMultipleGroupInfo.
  293. ----------------------------------------------------------------------------*/
  294.  
  295. static void DoOneGroupCmdResponse (long responseCode, CStr255 response, 
  296.     Ptr userDataPtr)
  297. {
  298.     TGetMultipleGroupInfo *z;
  299.     NntpGroupInfoPtr x;
  300.     char *p;
  301.     char state;
  302.     NntpGroupInfoHandle groupInfo;
  303.     long index;
  304.  
  305.     z = (TGetMultipleGroupInfo*)userDataPtr;
  306.     if (z->serverError) return;
  307.     groupInfo = z->groupInfo;
  308.     index = z->index;
  309.     state = MyHGetState(groupInfo);
  310.     MyHLock(groupInfo);
  311.     x = *groupInfo + index;
  312.     if (responseCode == 211) {
  313.         p = response;
  314.         CrackNum(&p);
  315.         x->count = CrackNum(&p);
  316.         x->first = CrackNum(&p);
  317.         x->last = CrackNum(&p);
  318.         x->ok = true;
  319.     } else if (responseCode == 502) {
  320.         x->count = 0;
  321.         x->first = 1;
  322.         x->last = 0;
  323.         x->ok = true;
  324.     } else if (responseCode == 411) {
  325.         x->ok = false;
  326.     } else {
  327.         z->serverError = true;
  328.     }
  329.     MyHSetState(groupInfo, state);
  330.     z->index = index + 1;
  331. }
  332.  
  333.  
  334.  
  335. /*----------------------------------------------------------------------------
  336.     SortHeadersCompare 
  337.     
  338.     This comparison function is used to sort a header info array into increasing 
  339.     order by article number.
  340.     
  341.     Entry:    p = pointer to first NntpHeaderInfo struct.
  342.             q = pointer to second NntpHeaderInfo struct.
  343.             
  344.     Exit:    function result = error code.
  345.             *result =
  346.                 -1 if p->number < q->number
  347.                  0 if p->number == q->number
  348.                  1 if p->number > q->number
  349. ----------------------------------------------------------------------------*/
  350.  
  351. static OSErr SortHeadersCompare (NntpHeaderInfoPtr p, NntpHeaderInfoPtr q,
  352.     short *result)
  353. {
  354.     OSErr err;
  355.     static short counter = 0;
  356.  
  357.     if ((++counter & 0x1f) == 0) {
  358.         err = (*gGiveTime)();
  359.         if (err != noErr) return err;
  360.         counter = 0;
  361.     }
  362.     
  363.     if (p->number < q->number) {
  364.         *result = -1;
  365.     } else if (p->number == q->number) {
  366.         *result = 0;
  367.     } else {
  368.         *result = 1;
  369.     }
  370.     return noErr;
  371. }
  372.  
  373.  
  374.  
  375. /*----------------------------------------------------------------------------
  376.     NntpInit 
  377.     
  378.     Initialize the module.
  379.     
  380.     Entry:    giveTime = pointer to GiveTime function.
  381.  
  382.     The GiveTime function is called during CPU-intensive operations.    
  383. ----------------------------------------------------------------------------*/
  384.  
  385. void NntpInit (NntpGiveTimeFunction giveTime)
  386. {
  387.     gGiveTime = giveTime;
  388. }
  389.  
  390.  
  391.  
  392. /*----------------------------------------------------------------------------
  393.     NntpIdle 
  394.     
  395.     Handle idle time tasks.
  396.     
  397.     Exit:    function result = result code (always noErr).
  398.     
  399.     This function checks all the open NNTP streams to see if their idle
  400.     timers have expired. Any streams with expired timers are closed.
  401. ----------------------------------------------------------------------------*/
  402.  
  403. OSErr NntpIdle (void)
  404. {
  405.     TStreamHandle s;
  406.     NetStreamRef netStream;
  407.     short idleTime;
  408.     unsigned long lastTransactionTime, curTime;
  409.     
  410.     GetDateTime(&curTime);
  411.     for (s = gStreamList; s != nil; s = (**s).next) {
  412.         netStream = (**s).netStream;
  413.         idleTime = (**s).options.idleTime;
  414.         if (netStream != nil && idleTime != 0) {
  415.             lastTransactionTime = (**s).lastTransactionTime;
  416.             if (curTime > lastTransactionTime + 60*idleTime) {
  417.                 NetClose(netStream);
  418.                 (**s).netStream = nil;
  419.             }
  420.         }
  421.     }
  422.     return noErr;
  423. }
  424.  
  425.  
  426.  
  427. /*----------------------------------------------------------------------------
  428.     NntpOpen 
  429.     
  430.     Open an NNTP stream.
  431.     
  432.     Entry:    host = server host address (domain name or dotted 
  433.                 decimal IP address).
  434.             options = pointer to server options, or nil to set default options.
  435.     
  436.     Exit:    function result = result code.
  437.             *stream = reference to opened stream, or nil if not opened.
  438.             
  439.     If the sendModeReader stream option is set, a "MODE READER" command is
  440.     sent to the server after connecting. This command is also sent in the
  441.     future on any automatic reconnects to the server. Some INN servers require
  442.     this command.
  443. ----------------------------------------------------------------------------*/
  444.  
  445. OSErr NntpOpen (char *host, NntpStreamOptions *options, NntpStreamRef *stream)
  446. {
  447.     TStreamHandle s = nil;
  448.     unsigned long addr;
  449.     unsigned short port;
  450.     NetStreamRef netStream;
  451.     CStr255 command, response;
  452.     long responseCode;
  453.     OSErr err = noErr;
  454.     
  455.     *stream = nil;
  456.     
  457.     err = MyNewHandle(sizeof(TStream), &s);
  458.     if (err != noErr) return err;
  459.     
  460.     err = NetNameToAddr(host, kNNTPPort, &addr, &port);
  461.     if (err != noErr) goto exit;
  462.     
  463.     err = NetOpen(addr, port, true, &netStream, &responseCode, response);
  464.     if (err != noErr) goto exit;
  465.     
  466.     (**s).netStream = netStream;
  467.     if (options == nil) {
  468.         (**s).options.idleTime = 0;
  469.         (**s).options.useXPAT = false;
  470.         (**s).options.sendModeReader = true;
  471.         (**s).options.batchedCmds = false;
  472.         (**s).options.newConnection = true;
  473.         (**s).options.authOnConnect = false;
  474.     } else {
  475.         (**s).options = *options;
  476.     }
  477.     (**s).serverHasXPAT = true;
  478.     (**s).addr = addr;
  479.     (**s).port = port;
  480.     *(**s).curGroup = 0;
  481.     (**s).next = gStreamList;
  482.     gStreamList = s;
  483.     *stream = (NntpStreamRef)s;
  484.     strcpy((**s).helloMsg, response);
  485.     
  486.     if (responseCode != 200 && responseCode != 201) return nntpServerErr;
  487.      
  488.     if (options == nil || options->sendModeReader) {
  489.         strcpy(command, "MODE READER");
  490.         err = NetCommand(netStream, command, &responseCode, response);
  491.         if (err != noErr) goto exit;
  492.     }
  493.     
  494.     if (options != nil && options->authOnConnect) {
  495.         err = Authorize(s);
  496.         if (err == nntpAuthFailedErr) return err;
  497.         if (err != noErr) goto exit;
  498.     }
  499.     
  500.     SetLastTransactionTime(s);
  501.  
  502.     return noErr;
  503.     
  504. exit:
  505.  
  506.     MyDisposeHandle(s);
  507.     *stream = nil;
  508.     return err;
  509.     
  510. }
  511.  
  512.  
  513.  
  514. /*----------------------------------------------------------------------------
  515.     NntpClose 
  516.     
  517.     Close an NNTP stream.
  518.     
  519.     Entry:    stream = stream reference.
  520.     
  521.     Exit:    function result = result code (always noErr).
  522. ----------------------------------------------------------------------------*/
  523.  
  524. OSErr NntpClose (NntpStreamRef stream)
  525. {
  526.     TStreamHandle s, prev, cur;
  527.     NetStreamRef netStream;
  528.     
  529.     s = (TStreamHandle)stream;
  530.     
  531.     for (prev = nil, cur = gStreamList; 
  532.         cur != nil && cur != s; 
  533.         prev = cur, cur = (**cur).next) /* do nothing */ ;
  534.     if (cur != nil) {
  535.         if (prev == nil) {
  536.             gStreamList = (**cur).next;
  537.         } else {
  538.             (**prev).next = (**cur).next;
  539.         }
  540.     }
  541.     
  542.     netStream = (**s).netStream;
  543.     if (netStream != nil) NetClose(netStream);
  544.     MyDisposeHandle(s);
  545.     return noErr;
  546. }
  547.  
  548.  
  549.  
  550. /*----------------------------------------------------------------------------
  551.     NntpAbort 
  552.     
  553.     Abort an NNTP stream.
  554.     
  555.     Entry:    stream = stream reference.
  556.     
  557.     Exit:    function result = result code (always noErr).
  558. ----------------------------------------------------------------------------*/
  559.  
  560. OSErr NntpAbort (NntpStreamRef stream)
  561. {
  562.     TStreamHandle s;
  563.     NetStreamRef netStream;
  564.     
  565.     s = (TStreamHandle)stream;
  566.     netStream = (**s).netStream;
  567.     if (netStream == nil) return noErr;
  568.     NetClose(netStream);
  569.     (**s).netStream = nil;
  570.     return noErr;
  571. }
  572.  
  573.  
  574.  
  575. /*----------------------------------------------------------------------------
  576.     NntpSetStreamOptions
  577.     
  578.     Set stream options.
  579.     
  580.     Entry:    stream = stream reference.
  581.             options = pointer to stream options.
  582.     
  583.     Exit:    function result = result code (always noErr).
  584. ----------------------------------------------------------------------------*/
  585.  
  586. OSErr NntpSetStreamOptions (NntpStreamRef stream, NntpStreamOptions *options)
  587. {
  588.     TStreamHandle s;
  589.     
  590.     s = (TStreamHandle)stream;
  591.     (**s).options = *options;
  592.     return noErr;
  593. }
  594.  
  595.  
  596.  
  597. /*----------------------------------------------------------------------------
  598.     NntpGetGroupNames 
  599.     
  600.     Get the full list of group names or a list of names for new groups 
  601.     from the server.
  602.     
  603.     Entry:    stream = stream reference.
  604.             time = 0 to get full group list.
  605.             time != 0 to only get new groups created since the specified time.
  606.             statusFilter = a C-format string of characters. Only the names of 
  607.                 groups with status not equal to one of the characters in this 
  608.                 string are returned. The string should be in lower case.
  609.     
  610.     Exit:    function result = result code.
  611.             strings = handle to group name strings.
  612.             numGroups = number of group names.
  613.             
  614.     The time parameter is in GetDateTime format (seconds since 01/01/04). It
  615.     is interpreted by the server, not by the Mac. To avoid missing new groups
  616.     due to differences between the server's time and the Mac's time, you
  617.     should subtract a healthy amount from the time of your last new groups 
  618.     check (e.g., 36 hours), then discard any duplicate groups returned.  
  619.             
  620.     The strings block contains the C-format group names. Group names are 
  621.     truncated to 127 characters.
  622. ----------------------------------------------------------------------------*/
  623.  
  624. OSErr NntpGetGroupNames (NntpStreamRef stream, unsigned long time,
  625.     char *statusFilter, Handle *strings, long *numGroups)
  626. {
  627.     TStreamHandle s;
  628.     NetStreamRef netStream;
  629.     DateTimeRec timeRec;
  630.     Handle text = nil;
  631.     char *p, *pEnd, *q, *r, status;
  632.     long n, len;
  633.     CStr255 command, response;
  634.     long responseCode;
  635.     OSErr err = noErr;
  636.     
  637.     s = (TStreamHandle)stream;
  638.     err = ReOpenConnection(s);
  639.     if (err != noErr) return err;
  640.     netStream = (**s).netStream;
  641.     
  642.     if (time == 0) {
  643.         strcpy(command, "LIST");
  644.     } else {
  645.         SecondsToDate(time, &timeRec);
  646.         timeRec.year = timeRec.year % 100;
  647.         sprintf(command, "NEWGROUPS %02d%02d%02d %02d%02d%02d", 
  648.             timeRec.year, timeRec.month, timeRec.day,
  649.             timeRec.hour, timeRec.minute, timeRec.second);
  650.     }
  651.     
  652.     err = NetCommand(netStream, command, &responseCode, response);
  653.     if (err != noErr) goto exit1;
  654.     if (responseCode != (time == 0 ? 215 : 231)) goto exit2;
  655.     
  656.     err = NetGetText(netStream, &text, nil, nil);
  657.     if (err != noErr) goto exit1;
  658.  
  659.     MyHLock(text);
  660.     p = q = *text;
  661.     pEnd = p + MyGetHandleSize(text);
  662.     n = 0;
  663.     while (p < pEnd) {
  664.         err = (*gGiveTime)();
  665.         if (err != noErr) goto exit3;
  666.         r = p;
  667.         while (*r != ' ' && *r != CR) r++;
  668.         len = r-p;
  669.         if (len > 127) len = 127;
  670.         while (*r == ' ') r++;
  671.         while (*r != ' ' && *r != CR) r++;
  672.         while (*r == ' ') r++;
  673.         while (*r != ' ' && *r != CR) r++;
  674.         while (*r == ' ') r++;
  675.         status = tolower(*r);
  676.         while (*r != CR) r++;
  677.         if (strchr(statusFilter, status) == nil) {
  678.             *(p+len) = 0;
  679.             strcpy(q, p);
  680.             q += len+1;
  681.             n++;
  682.         }
  683.         p = r+1;
  684.     }
  685.     len = q-*text;
  686.     MyHUnlock(text);
  687.     
  688.     MySetHandleSize(text, len);
  689.     *strings = text;
  690.     *numGroups = n;
  691.     SetLastTransactionTime(s);
  692.  
  693.     return noErr;
  694.     
  695. exit1:
  696.  
  697.     MyDisposeHandle(text);
  698.     (**s).netStream = nil;
  699.     return err;
  700.     
  701. exit2:
  702.     
  703.     MyDisposeHandle(text);
  704.     return nntpServerErr;
  705.     
  706. exit3:
  707.  
  708.     MyDisposeHandle(text);
  709.     return err;
  710. }
  711.  
  712.  
  713.  
  714. /*----------------------------------------------------------------------------
  715.     NntpGetGroupInfo 
  716.     
  717.     Get info for a single group.
  718.     
  719.     Entry:    stream = stream reference.
  720.             group = C-format group name.
  721.     
  722.     Exit:    function result = result code.
  723.             first = first article number in group.
  724.             last = last article number in group.
  725.             count = server's estimate of number of articles in the group.
  726.             
  727.     This function returns nntpNoSuchGroupErr if the group does not exist.
  728.     
  729.     If the newConnection stream option is set, the current connection to 
  730.     the news server is closed and a new one is opened before getting the 
  731.     group information. You need to do this with some kinds of news servers
  732.     (e.g., the reference implementation server). This close/reopen is
  733.     not done, however, if the previous call to NntpGetGroupInfo was 
  734.     within 10 seconds ago. This prevents rapid sequences of calls to
  735.     NntpGetGroupInfo from closing and reopening.
  736. ----------------------------------------------------------------------------*/
  737.  
  738. OSErr NntpGetGroupInfo (NntpStreamRef stream, char *group, 
  739.     long *first, long *last, long *count)
  740. {
  741.     TStreamHandle s;
  742.     NetStreamRef netStream;
  743.     CStr255 command, response;
  744.     long responseCode;
  745.     OSErr err = noErr;
  746.     char *p;
  747.     static long prevTick = 0;
  748.     
  749.     s = (TStreamHandle)stream;
  750.     
  751.     if ((**s).options.newConnection && TickCount() > prevTick + 10*60) {
  752.         netStream = (**s).netStream;
  753.         if (netStream != nil) {
  754.             NetClose(netStream);
  755.             (**s).netStream = nil;
  756.         }
  757.     }
  758.     
  759.     err = ReOpenConnection(s);
  760.     if (err != noErr) return err;
  761.     netStream = (**s).netStream;
  762.     
  763.     *(**s).curGroup = 0;
  764.     
  765.     sprintf(command, "GROUP %.127s", group);
  766.     err = NetCommand(netStream, command, &responseCode, response);
  767.     if (err != noErr) goto exit1;
  768.     if (responseCode == 502) {
  769.         *count = 0;
  770.         *first = 1;
  771.         *last = 0;
  772.     } else if (responseCode == 211) {
  773.         p = response;
  774.         CrackNum(&p);
  775.         *count = CrackNum(&p);
  776.         *first = CrackNum(&p);
  777.         *last = CrackNum(&p);
  778.     } else if (responseCode == 411) {
  779.         return nntpNoSuchGroupErr;
  780.     } else {
  781.         return nntpServerErr;
  782.     }
  783.  
  784.     SetCurrentGroup(s, group);
  785.     SetLastTransactionTime(s);
  786.     prevTick = TickCount();
  787.     
  788.     return noErr;
  789.     
  790. exit1:
  791.  
  792.     (**s).netStream = nil;
  793.     return err;
  794. }
  795.  
  796.  
  797.  
  798. /*----------------------------------------------------------------------------
  799.     NntpGetMultipleGroupInfo 
  800.     
  801.     Get info for multiple groups.
  802.     
  803.     Entry:    stream = stream reference.
  804.             info = handle to array of group info.
  805.             numGroups = number of groups in group info array.
  806.             strings = handle to group name strings.
  807.     
  808.     Exit:    function result = result code.
  809.             
  810.     For each group in the array, the following information is returned in
  811.     the group info array:
  812.     
  813.         first = first article number in group.
  814.         last = last article number in group.
  815.         count = estimate of number of articles in the group.
  816.         ok = true if other info is valid, false if group does not exist
  817.             or some other server error occured.
  818.     
  819.     If the newConnection stream option is set, the current connection to 
  820.     the news server is closed and a new one is opened before getting the 
  821.     group information. You need to do this with some kinds of news servers
  822.     (e.g., the reference implementation server).
  823.     
  824.     If the batchedCmds stream option is set, multiple GROUP commands are 
  825.     sent to the news server in a batch, and the responses are processed as
  826.     they arrive. If the batchedCmds stream option is not set, the GROUP 
  827.     commands are sent one at a time. Batched commands are much faster, but
  828.     they do not work with all servers.
  829. ----------------------------------------------------------------------------*/
  830.  
  831. OSErr NntpGetMultipleGroupInfo (NntpStreamRef stream, NntpGroupInfoHandle info,
  832.     long numGroups, Handle strings)
  833. {
  834.     TStreamHandle s;
  835.     NetStreamRef netStream;
  836.     NntpGroupInfoPtr x, xEnd;
  837.     Boolean savedNewConnectionOption;
  838.     long cmdBufSize;
  839.     Handle cmdBuf;
  840.     char *p;
  841.     short len;
  842.     short state1, state2;
  843.     TGetMultipleGroupInfo z;
  844.     OSErr err = noErr;
  845.     
  846.     state1 = MyHGetState(info);
  847.     state2 = MyHGetState(strings);
  848.     
  849.     if (numGroups == 0) return noErr;
  850.     
  851.     s = (TStreamHandle)stream;
  852.     
  853.     if ((**s).options.newConnection) {
  854.         netStream = (**s).netStream;
  855.         if (netStream != nil) {
  856.             NetClose(netStream);
  857.             (**s).netStream = nil;
  858.         }
  859.     }
  860.     
  861.     err = ReOpenConnection(s);
  862.     if (err != noErr) return err;
  863.     netStream = (**s).netStream;
  864.     
  865.     *(**s).curGroup = 0;
  866.     
  867.     if ((**s).options.batchedCmds) {
  868.     
  869.         cmdBufSize = 0;
  870.         xEnd = *info + numGroups;
  871.         for (x = *info; x < xEnd; x++) 
  872.             cmdBufSize += 7 + strlen(*strings + x->offset);
  873.         err = MyNewHandle(cmdBufSize, &cmdBuf);
  874.         if (err != noErr) return err;
  875.         p = *cmdBuf;
  876.         xEnd = *info + numGroups;
  877.         for (x = *info; x < xEnd; x++) {
  878.             BlockMoveData("GROUP ", p, 6);
  879.             p += 6;
  880.             len = strlen(*strings + x->offset);
  881.             BlockMoveData(*strings + x->offset, p, len);
  882.             p += len;
  883.             *p++ = CR;
  884.         }
  885.         z.groupInfo = info;
  886.         z.index = 0;
  887.         z.serverError = false;
  888.         err = NetBatchedCommands(netStream, cmdBuf, DoOneGroupCmdResponse, (Ptr)&z);
  889.         MyDisposeHandle(cmdBuf);
  890.         if (err != noErr) goto exit1;
  891.         if (z.serverError) return nntpServerErr;
  892.     
  893.     } else {
  894.     
  895.         savedNewConnectionOption = (**s).options.newConnection;
  896.         (**s).options.newConnection = false;
  897.         MyHLock(info);
  898.         MyHLock(strings);
  899.         xEnd = *info + numGroups;
  900.         for (x = *info; x < xEnd; x++) {
  901.             err = NntpGetGroupInfo(stream, *strings + x->offset,
  902.                 &x->first, &x->last, &x->count);
  903.             if (err == noErr) {
  904.                 x->ok = true;
  905.             } else if (err == nntpNoSuchGroupErr || err == nntpServerErr) {
  906.                 x->ok = false;
  907.             } else {
  908.                 goto exit2;
  909.             }
  910.         }
  911.         MyHSetState(info, state1);
  912.         MyHSetState(strings, state2);
  913.         (**s).options.newConnection = savedNewConnectionOption;
  914.     
  915.     }
  916.  
  917.     SetLastTransactionTime(s);
  918.     
  919.     return noErr;
  920.     
  921. exit1:
  922.  
  923.     MyHSetState(info, state1);
  924.     MyHSetState(strings, state2);
  925.     (**s).netStream = nil;
  926.     return err;
  927.  
  928. exit2:
  929.  
  930.     MyHSetState(info, state1);
  931.     MyHSetState(strings, state2);
  932.     (**s).options.newConnection = savedNewConnectionOption;
  933.     return err;
  934.  
  935. }
  936.  
  937.  
  938.  
  939. /*----------------------------------------------------------------------------
  940.     NntpGetHeaders 
  941.     
  942.     Get selected article headers.
  943.     
  944.     Entry:    stream = stream reference.
  945.             group = group name.
  946.             first = first article number.
  947.             last = last article number.
  948.             header = header name.
  949.             pattern = search string.
  950.             buildXPAT = pointer to function to build XPAT pattern
  951.                 for search string. Ignored if pattern nil or empty.
  952.             matchPattern = pointer to function to compare search string
  953.                 to target string. Ignored if pattern nil or empty.
  954.             
  955.     Exit:    function result = result code.
  956.             info = handle to array of header info.
  957.             strings = handle to header strings.
  958.             numHeaders = number of headers.
  959.     
  960.     If the pattern parameter is nil or the empty string, all headers in the 
  961.     range [first,last] are returned. 
  962.     
  963.     If the pattern parameter is not nil or empty, only headers in the range
  964.     [first,last] which match the pattern are returned.
  965.             
  966.     The strings block contains the C-format header strings, one after another.
  967.     The offset fields in the header info array contain offsets into this
  968.     block. Header strings are truncated to 255 characters, and have leading
  969.     and trailing blanks stripped.
  970.     
  971.     All returned elements of the header info array have article numbers in 
  972.     the range [first,last]. The header info array is sorted by article number, 
  973.     with any duplicates eliminated.
  974.     
  975.     If the useXPAT stream option is set, and if a search pattern is specified,
  976.     the buildXPAT callback function is called to build the regular expression
  977.     XPAT pattern. An XPAT command is then sent to the server to get the 
  978.     matching headers.
  979.     
  980.     If the useXPAT stream option is not set, or if the server does not support
  981.     the XPAT command, all the headers are fetched from the server, and the
  982.     matchPattern callback function is called to extract the matching headers.
  983. ----------------------------------------------------------------------------*/
  984.  
  985. OSErr NntpGetHeaders (NntpStreamRef stream, char *group, long first, long last,
  986.     char *header, char *pattern,
  987.     void (*buildXPAT)(char *pattern, char *regExp, short regExpLen),
  988.     Boolean (*matchPattern)(char *pattern, char *string),
  989.     NntpHeaderInfoHandle *info, Handle *strings, long *numHeaders)
  990. {
  991.     TStreamHandle s;
  992.     NetStreamRef netStream;
  993.     CStr255 command, response;
  994.     long responseCode;
  995.     OSErr err = noErr;
  996.     Handle text = nil;
  997.     NntpHeaderInfoHandle theInfo = nil;
  998.     NntpHeaderInfoPtr x;
  999.     char *p, *pEnd, *q, *r, *t;
  1000.     long n, len, number, prevNumber;
  1001.     char xpatCmd[1000];
  1002.     Boolean mustFilter;
  1003.     Boolean mustSort = false;
  1004.     long numToMove;
  1005.     
  1006.     s = (TStreamHandle)stream;
  1007.     err = ReOpenConnection(s);
  1008.     if (err != noErr) return err;
  1009.     netStream = (**s).netStream;
  1010.     
  1011.     if (!MyStrEqual(group, (**s).curGroup)) {
  1012.         *(**s).curGroup = 0;
  1013.         sprintf(command, "GROUP %.127s", group);
  1014.         err = NetCommand(netStream, command, &responseCode, response);
  1015.         if (err != noErr) goto exit1;
  1016.         if (responseCode == 411) return nntpNoSuchGroupErr;
  1017.         if (responseCode != 211) return nntpServerErr;
  1018.         SetCurrentGroup(s, group);
  1019.     }
  1020.         
  1021.     while (true) {
  1022.         if (pattern == nil || *pattern == 0 || !(**s).options.useXPAT || !(**s).serverHasXPAT) {
  1023.             sprintf(command, "XHDR %.127s %ld-%ld", header, first, last);
  1024.             err = NetCommand(netStream, command, &responseCode, response);
  1025.             if (err != noErr) goto exit1;
  1026.             if (responseCode != 221) return nntpServerErr;
  1027.             mustFilter = pattern != nil && *pattern != 0;
  1028.             break;
  1029.         } else {
  1030.             sprintf(xpatCmd, "XPAT %.127s %ld-%ld ", header, first, last);
  1031.             len = strlen(xpatCmd);
  1032.             (*buildXPAT)(pattern, xpatCmd + len, 1000 - len);
  1033.             len = strlen(xpatCmd);
  1034.             if (len > 255) len = 255;
  1035.             BlockMoveData(xpatCmd, command, len);
  1036.             *(command + len) = 0;
  1037.             err = NetCommand(netStream, xpatCmd, &responseCode, response);
  1038.             if (err != noErr) goto exit1;
  1039.             if (responseCode == 500) {
  1040.                 (**s).serverHasXPAT = false;
  1041.                 continue;
  1042.             }
  1043.             if (responseCode != 221) return nntpServerErr;
  1044.             mustFilter = false;
  1045.             break;
  1046.         }
  1047.     }
  1048.     
  1049.     err = NetGetText(netStream, &text, nil, nil);
  1050.     if (err != noErr) goto exit1;
  1051.  
  1052.     pEnd = *text + MyGetHandleSize(text);
  1053.     n = 0;
  1054.     for (p = *text; p < pEnd; p++) if (*p == CR) n++;
  1055.     
  1056.     err = MyNewHandle(n*sizeof(NntpHeaderInfo), &theInfo);
  1057.     if (err != noErr) goto exit3;
  1058.  
  1059.     MyHLock(text);
  1060.     MyHLock(theInfo);
  1061.     x = *theInfo;
  1062.     p = q = *text;
  1063.     pEnd = *text + MyGetHandleSize(text);
  1064.     n = 0;
  1065.     while (p < pEnd) {
  1066.         number = CrackNum(&p);
  1067.         if (number < first || number > last) {
  1068.             while (*p != CR) p++;
  1069.             p++;
  1070.             continue;
  1071.         }
  1072.         while (*p == ' ') p++;
  1073.         r = p;
  1074.         while (*r != CR) r++;
  1075.         t = r-1;
  1076.         while (t >= p && *t == ' ') t--;
  1077.         len = t-p+1;
  1078.         if (len > 255) len = 255;
  1079.         *(p+len) = 0;
  1080.         if (!mustFilter || (*matchPattern)(pattern, p)) {
  1081.             if (n > 0 && number <= prevNumber) mustSort = true;
  1082.             prevNumber = number;
  1083.             x->number = number;
  1084.             x->offset = q - *text;
  1085.             strcpy(q, p);
  1086.             q += len+1;
  1087.             n++;
  1088.             x++;
  1089.         }
  1090.         p = r+1;
  1091.     }
  1092.     if (mustSort) {
  1093.         err = FastQSort(*theInfo, n, sizeof(NntpHeaderInfo), 
  1094.             (SortCmpFunction)SortHeadersCompare);
  1095.         if (err != noErr) goto exit3;
  1096.         if (n > 1) {
  1097.             x =  *theInfo;
  1098.             for (numToMove = (n-1)*sizeof(NntpHeaderInfo); 
  1099.                 numToMove > 0; 
  1100.                 numToMove -= sizeof(NntpHeaderInfo))
  1101.             {
  1102.                 if (x->number == (x+1)->number) {
  1103.                     BlockMoveData(x+1, x, numToMove);
  1104.                     n--;
  1105.                 } else {
  1106.                     x++;
  1107.                 }
  1108.             }
  1109.         }
  1110.     }
  1111.     len = q - *text;
  1112.     MyHUnlock(text);
  1113.     MyHUnlock(theInfo);
  1114.     
  1115.     MySetHandleSize(text, len);
  1116.     MySetHandleSize(theInfo, n*sizeof(NntpHeaderInfo));
  1117.     *info = theInfo;
  1118.     *strings = text;
  1119.     *numHeaders = n;
  1120.     SetLastTransactionTime(s);
  1121.     
  1122.     return noErr;
  1123.     
  1124. exit1:
  1125.  
  1126.     MyDisposeHandle(text);
  1127.     MyDisposeHandle(theInfo);
  1128.     (**s).netStream = nil;
  1129.     return err;
  1130.     
  1131. exit2:
  1132.     
  1133.     MyDisposeHandle(text);
  1134.     MyDisposeHandle(theInfo);
  1135.     return nntpServerErr;
  1136.     
  1137. exit3:
  1138.  
  1139.     MyDisposeHandle(text);
  1140.     MyDisposeHandle(theInfo);
  1141.     return err;
  1142. }
  1143.  
  1144.  
  1145.  
  1146. /*----------------------------------------------------------------------------
  1147.     NntpGetArticle 
  1148.     
  1149.     Get an article.
  1150.     
  1151.     Entry:    stream = stream reference.
  1152.             group = group name, or nil if fetching by message id.
  1153.             number = article number. Ignored if fetching by message id.
  1154.             id = message id string, including < and > delimiters. Ignored
  1155.                 if fetching by article number.
  1156.             part = which part of the article to get:
  1157.                 "ARTICLE": full article text, header and body.
  1158.                 "HEAD": only article header.
  1159.                 "BODY": only article body.
  1160.             text = pointer to handle in which to return text,
  1161.                 or nil if none.
  1162.             chunkFunction = pointer to chunk processing function, 
  1163.                 or nil if none.
  1164.             userDataPtr = pointer to user data to be passed through
  1165.                 to the chunk processing function, or nil if none.
  1166.     
  1167.     Exit:    function result = error code.
  1168.             *text = handle to article text, if text != nil.
  1169.     
  1170.     The chunk processing function, if any, is called every time a new 
  1171.     block of article text is received from the server. This function must be 
  1172.     declared as follows:
  1173.     
  1174.     OSErr ProcessTextChunk (Ptr t, long tLen, Ptr userDataPtr,
  1175.         long *truncPos)
  1176.     
  1177.     Entry:    t = pointer to raw text received from server.
  1178.             tLen = length of text received from server.
  1179.             userDataPtr = pointer to user data.
  1180.             
  1181.     Exit:    function result = error code.
  1182.             *truncPos = position at which to truncate text if
  1183.                 error code is netTruncatedErr.
  1184.     
  1185.     If text == nil, the article text is processed by the chunk processing 
  1186.     function in pieces (e.g., saved to a file as it comes in over the 
  1187.     network). In this case, the text is not accumulated and returned as a 
  1188.     whole to the NntpGetArticle caller, and the "t" and "tLen" parameters 
  1189.     passed to the chunk processing function are for just the chunk received.
  1190.     
  1191.     If text != nil, the text is accumulated as it is received and returned
  1192.     as a whole to the NntpGetArticle caller, and the "t" and "tLen" parameters
  1193.     passed to the chunk processing function (if any) are for the entire text
  1194.     as accumulated so far.
  1195.                 
  1196.     If the chunk processing function returns with an error code, NntpGetArticle 
  1197.     aborts the stream and returns to the caller immediately with the text handle
  1198.     set to just the text received so far. The error code returned by the
  1199.     chunk processing function is returned to the NntpGetArticle caller as 
  1200.     NntpGetArticle's function result.
  1201.     
  1202.     The text passed to the chunk processing function is raw. It contains CRLF 
  1203.     line terminators and doubled leading ".." characters. The terminating "."
  1204.     on a line by itself, however, is not passed to the chunk processing function.
  1205. ----------------------------------------------------------------------------*/
  1206.  
  1207. OSErr NntpGetArticle (NntpStreamRef stream, char *group, long number, 
  1208.     char *id, char *part, Handle *text, NetChunkFunction chunkFunction,
  1209.     Ptr userDataPtr)
  1210. {
  1211.     TStreamHandle s;
  1212.     NetStreamRef netStream;
  1213.     CStr255 command, response;
  1214.     long responseCode;
  1215.     OSErr err = noErr;
  1216.     
  1217.     s = (TStreamHandle)stream;
  1218.     err = ReOpenConnection(s);
  1219.     if (err != noErr) return err;
  1220.     netStream = (**s).netStream;
  1221.     
  1222.     if (group != nil && !MyStrEqual(group, (**s).curGroup)) {
  1223.         *(**s).curGroup = 0;
  1224.         sprintf(command, "GROUP %.127s", group);
  1225.         err = NetCommand(netStream, command, &responseCode, response);
  1226.         if (err != noErr) goto exit;
  1227.         if (responseCode == 411) return nntpNoSuchGroupErr;
  1228.         if (responseCode != 211) return nntpServerErr;
  1229.         SetCurrentGroup(s, group);
  1230.     }
  1231.  
  1232.     if (group != nil) {
  1233.         sprintf(command, "%s %ld", part, number);
  1234.     } else {
  1235.         sprintf(command, "%s %.127s", part, id);
  1236.     }
  1237.     err = NetCommand(netStream, command, &responseCode, response);
  1238.     if (err != noErr) goto exit;
  1239.     if (responseCode == 423 || responseCode == 430) return nntpNoSuchArticleErr;
  1240.     if (responseCode != 220 && responseCode != 221 &&
  1241.         responseCode != 222) return nntpServerErr;
  1242.         
  1243.     err = NetGetText(netStream, text, chunkFunction, userDataPtr);
  1244.     if (err != noErr) goto exit;
  1245.  
  1246.     SetLastTransactionTime(s);
  1247.     
  1248.     return noErr;
  1249.     
  1250. exit:
  1251.  
  1252.     (**s).netStream = nil;
  1253.     return err;
  1254. }
  1255.  
  1256.  
  1257.  
  1258. /*----------------------------------------------------------------------------
  1259.     NntpPostArticle 
  1260.     
  1261.     Post an article.
  1262.     
  1263.     Entry:    stream = stream reference.
  1264.             text = handle to article text, including header lines, 
  1265.                 with CR line terminators.
  1266.                 Warning: the memory block is modified by the function.
  1267.                 The memory block must be unlocked and nonpurgeable.
  1268.             
  1269.     Exit:    function result = result code.
  1270.             postIndeterminate = true if entire article sent, but error occured
  1271.                 or user canceled before final server response received. The
  1272.                 article may or may not have been posted successfully.
  1273. ----------------------------------------------------------------------------*/
  1274.  
  1275. OSErr NntpPostArticle (NntpStreamRef stream, Handle text, Boolean *postIndeterminate)
  1276. {
  1277.     TStreamHandle s;
  1278.     NetStreamRef netStream;
  1279.     CStr255 command, response;
  1280.     long responseCode;
  1281.     OSErr err = noErr;
  1282.     
  1283.     *postIndeterminate = false;
  1284.     
  1285.     s = (TStreamHandle)stream;
  1286.     err = ReOpenConnection(s);
  1287.     if (err != noErr) return err;
  1288.     netStream = (**s).netStream;
  1289.     
  1290.     strcpy(command, "POST");
  1291.     err = NetCommand(netStream, command, &responseCode, response);
  1292.     if (err != noErr) goto exit1;
  1293.     if (responseCode != 340) return nntpServerErr;
  1294.     
  1295.     err = NetPutText(netStream, text);
  1296.     if (err != noErr) goto exit1;
  1297.     
  1298.     *postIndeterminate = true;
  1299.     
  1300.     err = NetGetExtraResponse(netStream, &responseCode, response);
  1301.     if (err != noErr) goto exit1;
  1302.     *postIndeterminate = false;
  1303.     if (responseCode != 240) return nntpServerErr;
  1304.  
  1305.     SetLastTransactionTime(s);
  1306.     
  1307.     return noErr;
  1308.     
  1309. exit1:
  1310.  
  1311.     (**s).netStream = nil;
  1312.     return err;
  1313. }
  1314.  
  1315.  
  1316.  
  1317. /*----------------------------------------------------------------------------
  1318.     NntpAuthorize 
  1319.     
  1320.     Authorize user.
  1321.     
  1322.     Entry:    stream = stream reference.
  1323.             
  1324.     Exit:    function result = result code.
  1325. ----------------------------------------------------------------------------*/
  1326.  
  1327. OSErr NntpAuthorize (NntpStreamRef stream)
  1328. {
  1329.     TStreamHandle s;
  1330.     OSErr err = noErr;
  1331.     
  1332.     s = (TStreamHandle)stream;
  1333.     err = ReOpenConnection(s);
  1334.     if (err != noErr) return err;
  1335.  
  1336.     err = Authorize(s);
  1337.     if (err != noErr) return err;
  1338.     
  1339.     SetLastTransactionTime(s);
  1340.     
  1341.     return noErr;
  1342. }
  1343.  
  1344.  
  1345.  
  1346. /*----------------------------------------------------------------------------
  1347.     NntpGetHello 
  1348.     
  1349.     Get the server hello message.
  1350.     
  1351.     Entry:    stream = stream reference.
  1352.             
  1353.     Exit:    function result = result code (always noErr).
  1354.             msg = hello message.
  1355. ----------------------------------------------------------------------------*/
  1356.  
  1357. OSErr NntpGetHello (NntpStreamRef stream, CStr255 msg)
  1358. {
  1359.     TStreamHandle s;
  1360.     
  1361.     s = (TStreamHandle)stream;
  1362.     strcpy(msg, (**s).helloMsg);
  1363.     return noErr;
  1364. }
  1365.  
  1366.  
  1367.  
  1368. /*----------------------------------------------------------------------------
  1369.     NntpGetHelp 
  1370.     
  1371.     Get the server help text.
  1372.     
  1373.     Entry:    stream = stream reference.
  1374.             
  1375.     Exit:    function result = result code.
  1376.             text = handle to help text, with CR-terminated lines.
  1377. ----------------------------------------------------------------------------*/
  1378.  
  1379. OSErr NntpGetHelp (NntpStreamRef stream, Handle *text)
  1380. {
  1381.     TStreamHandle s;
  1382.     NetStreamRef netStream;
  1383.     CStr255 command, response;
  1384.     long responseCode;
  1385.     OSErr err = noErr;
  1386.     
  1387.     s = (TStreamHandle)stream;
  1388.     err = ReOpenConnection(s);
  1389.     if (err != noErr) return err;
  1390.     netStream = (**s).netStream;
  1391.     
  1392.     strcpy(command, "HELP");
  1393.     err = NetCommand(netStream, command, &responseCode, response);
  1394.     if (err != noErr) goto exit1;
  1395.     if (responseCode != 100) return nntpServerErr;
  1396.     
  1397.     err = NetGetText(netStream, text, nil, nil);
  1398.     if (err != noErr) goto exit1;
  1399.  
  1400.     SetLastTransactionTime(s);
  1401.     
  1402.     return noErr;
  1403.     
  1404. exit1:
  1405.  
  1406.     (**s).netStream = nil;
  1407.     return err;
  1408. }
  1409.  
  1410.  
  1411.  
  1412. /*----------------------------------------------------------------------------
  1413.     NntpGetIPAddr 
  1414.     
  1415.     Get server IP address.
  1416.         
  1417.     Entry:    stream = stream reference.
  1418.             
  1419.     Exit:    function result = result code (always noErr).
  1420.             ipAddr = IP address of news server.
  1421. ----------------------------------------------------------------------------*/
  1422.  
  1423. OSErr NntpGetIPAddr (NntpStreamRef stream, unsigned long *ipAddr)
  1424. {
  1425.     TStreamHandle s;
  1426.     
  1427.     s = (TStreamHandle)stream;
  1428.     *ipAddr = (**s).addr;
  1429.     return noErr;
  1430. }
  1431.  
  1432.  
  1433.  
  1434. /*----------------------------------------------------------------------------
  1435.     NntpGetServerErrInfo 
  1436.     
  1437.     Get server error information.
  1438.     
  1439.     Entry:    stream = stream reference.
  1440.     
  1441.     Exit:    *serverErrInfo = server error information.
  1442. ----------------------------------------------------------------------------*/
  1443.  
  1444. void NntpGetServerErrInfo (NntpStreamRef stream, NetServerErrInfo *serverErrInfo)
  1445. {
  1446.     TStreamHandle s;
  1447.     NetStreamRef netStream;
  1448.     
  1449.     s = (TStreamHandle)stream;
  1450.     netStream = (**s).netStream;
  1451.     if (netStream == nil) {
  1452.         *(serverErrInfo->command) = 0;
  1453.         *(serverErrInfo->response) = 0;
  1454.         serverErrInfo->responseCode = 0;
  1455.     } else {
  1456.         NetGetServerErrInfo(netStream, serverErrInfo);
  1457.         if (serverErrInfo->responseCode == 400) {
  1458.             NetClose(netStream);
  1459.             (**s).netStream = nil;
  1460.         }
  1461.     }
  1462. }
  1463.